fix(platform-objects): attestFreshDatastore looks its os migrate remedy up, never defaults it - #16180
Conversation
…r defaults it (#16067) `attestFreshDatastore`'s contradiction warning built its `os migrate` sentence from a two-way branch: the file-references id got `files-to-references` and EVERY other id got `value-shapes` by default. `CREATION_ATTESTED_MIGRATION_IDS` has had three members since the ADR-0030 cut-over id joined it, and for that third id the default is a wrong prescription — `os migrate value-shapes --apply` neither attests nor clears it, and there is no `os migrate notification-event` to send an operator to at all (measured: no such sub-command exists under `packages/cli/src/commands/migrate/`). Replaced with an explicit id -> remedy register that is TOTAL over the ids a value-shape tally can contradict, and the loop now asks it instead of falling into an arm: an id with no value-shape contract is never-contradictable by this evidence and is attested on the birth observation. A new member therefore inherits NO remedy — adding a third arm that happened to be right today would only have moved the same defect onto the fourth member. A `Map` rather than an object literal: `id` arrives from a caller-supplied array and an object literal would answer `'toString'` with a function. Pins the default, which is where the defect lived: a contradiction fed for a non-ADR-0104 id asserts the warning does not name `value-shapes`, and a case total over `CREATION_ATTESTED_MIGRATION_IDS` asserts no id is ever handed another migration's command. A pin over only the two known ids passed on the broken code. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
…16067) Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARYe3yQTQCUFm5qPYNgKaJ
📓 Docs Drift Check3 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 2 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 58c897c48f044a8e72340f21b5e881a607e52c9d && git checkout 58c897c48f044a8e72340f21b5e881a607e52c9d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d5d8d50db2b698107ee4b36ef7b987e6ed23da0c 5eb248ff2b6edee89e47ec27e35ade10fa15d93b && git checkout -B drift-repro d5d8d50db2b698107ee4b36ef7b987e6ed23da0c && git merge --no-ff 5eb248ff2b6edee89e47ec27e35ade10fa15d93b
node scripts/docs-audit/affected-docs.mjs --json d5d8d50db2b698107ee4b36ef7b987e6ed23da0c |
Fixes #16067
attestFreshDatastorebuilt its operator-facing remedy sentence from a two-way branch:CREATION_ATTESTED_MIGRATION_IDShas three members. Theelsearm handedos migrate value-shapes --applyto every id that is not the file one — includingadr-0030-notification-event, which that command neither attests nor clears.The first measurement, because the card's grade rested on it
The card argued this is not a live defect because the engine tallies admitted
violations under the two ADR-0104 ids only. Triage counted 9 references in
packages/objectql/src/engine.tsand said in as many words that it had notclassified which are tally keys. That classification decides the scope, so it was
re-run first. All 9 lines, on
origin/main81b426f77:readMigrationFlagMemoizedinisFileReferencesMigrationVerifiedisValueShapesMigrationVerifiednoteAdmittedValueShapeViolationrecordObservedDeviation(a consumer of the key)retractCreationAttestation(a consumer of the key)kernel:bootstrappedadvisoryNOTIFICATION_EVENT_MIGRATION_IDappears zero times inengine.ts. Line 8029 derivesits key from
violation.gate, declared'media' | 'value-shape'invalidation/record-validator.ts:1050— a closed two-member union — and it is the onlywriter of
admittedValueShapeViolations. So the shipped engine cannot key that tally witha third id, and the card's central claim holds.
Population control, because the pathspec trap is live in this tree:
git grep -c MIGRATION_ID -- packages/objectql/srcfindsengine.tswith 9 hits, while the same queryspelled
packages/objectql/src/**/*.tssilently returns onlyvalidation/record-validator.tsand drops
engine.tsentirely.What the classification does not license
valueShapeViolationsAdmittedis an optional, duck-typed member ofMigrationFlagEngine(migration-flag.ts:53) returning an openRecord<string, ...>.Any object satisfies it — the doubles in this repo's own tests already do. So the wrong
sentence was one non-ObjectQL producer away from an operator's terminal;
"unreachable" was a property of today's single producer, never of the seam. Grade
unchanged (
finding/ p3 both stand), and the fix is written so the seam cannotproduce a wrong prescription regardless of who implements it.
The fix
An explicit register, total over the ids a value-shape tally can contradict, and the
loop asks it rather than falling into an arm. An id absent from it is
never-contradictable by this evidence — a value-shape tally says nothing about a fact
that is not about value shapes — and is attested on the birth observation as before.
So a fourth member inherits no remedy; a third arm that happened to be right today
would only have moved the same defect one member along.
A
Map, not an object literal:idarrives from a caller-supplied array, and an objectliteral answers
'toString'with a function.os migrate notification-eventdoes not exist — measured, not recalled: no such fileunder
packages/cli/src/commands/migrate/, with a firing control (value-shapesmatches3 files there). So for that id the correct sentence is no sentence, which is the
route this PR chose over inventing one.
Pins, and the ablation that proves they bite
Two, both aimed at the DEFAULT, which is where the defect lived:
does not name
value-shapes;CREATION_ATTESTED_MIGRATION_IDSasserts no id is ever handedanother migration's command — so a fourth member is judged the moment it is added.
Ablation (fix committed first, mutation proven on disk by anchored counts in both
directions, absolute-path
traprestore, restore proven by blob equality with the HEADblob and an empty
git diff HEAD): with the two-way branch put back, exactly the 2 newcases red and the pre-existing 22 stay green. That is the card's point measured — a pin
over only the two known ids passes on the broken code.
Verification
Union re-run at
5eb248ff2.pnpm --filter '@objectstack/platform-objects^...' build— VERDICTcommand-exit 0pnpm --filter @objectstack/platform-objects test— 35 files, 532 passedpnpm --filter @objectstack/platform-objects typecheck— VERDICTcommand-exit 0.**/*.test.ts, andtsc --listFilesfinds the test file 0 times against a control of 1 for the source.A targeted
tsc --noEmit --ignoreConfigrun with the same strictness over the testfile reports 0 errors.
@objectstack/objectqladr0104-attestation-evidence+adr0104-lax-deviation-marker— 19 passed (after building that closure; the firstattempt was an unbuilt-dependency
Cannot find packageand is reported as such, notas a red)
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack(no hand-supplied paths): 56 families, all run, 53 exit 0. The 3 non-zero are
explicit NOT-MEASURED prerequisites, not findings:
check:react-declaration-parity("this gate did NOT run", no
MANIFEST),check:dual-build-cjs-loadsandcheck:published-readme-exports(exit 3,PREREQUISITE NOT MET, need a fullpnpm build). Exit codes captured by redirect-then-read, never through a pipe.check:dts-closuregreen is about this package: it swept 8 built packages andpackages/platform-objectsis one of them (58/58 declaration files across the 8).check:partof-closing-keywordpassed its 28 self-test cases only — it readsPR_BODYfrom the environment, so that run was vacuous. Re-run against this postedbody; result recorded on the card.
Clause ②: no
Limb 1 — published surface. A real ablation: built at head, swapped
migration-flag.tsback toorigin/main81b426f77, rebuilt, diffed all 22declaration files this package publishes (not the root barrel — that trap is why the
census is total), restored and rebuilt. Rebuild proven, not assumed: 66/66 dist mtimes
moved on each leg. All 22 declarations byte-identical; 12 files differ and every one
is a
.js/.mjsbundle or its sourcemap. Controls: the new register appears twice ineach of the 3 built JS entry bundles (instrument live) and in 0 declaration files;
attestFreshDatastorefires indist/system/index.d.ts(3) anddist/index.d.ts(1),so the census reached the subpath where the symbol actually lives. This package emits no
content-hashed shared chunk. The remedy string literals appear in 0 declaration files, so
#16076's standing ruling about a literal value inside an emitted data type does not even
arise here.
Limb 2 — accept/reject behaviour. No. For the two gate-bearing ADR-0104 ids the
behaviour is unchanged, and that is measured rather than asserted: under the ablation the
2 parameterised ADR-0104 cases pass on both the fixed and the unfixed code, alongside the
other 20. The only outcome that moves belongs to an id with no value-shape contract,
which
NOTIFICATION_EVENT_MIGRATION_ID's own spec docblock declares "Receipt, not gate.Nothing reads a row under this id as a precondition, and nothing may". No metadata, record
or request is newly accepted or rejected, and on the shipped engine the returned attested
array is identical either side because the tally cannot name that id at all.
Scope
Read-only surfaces respected:
packages/specanddocs/adr/**are read, never edited.#15710 is left alone — that ruling added the third member and stands.
Out-of-scope observations found while measuring are filed separately rather than ridden
here; they are listed on the card.
Generated by Claude Code